﻿using System;
using System.Linq;
using System.Text;
using System.Web;
using Lbb.Security;
#if DEBUG
using System.Diagnostics;
#endif

namespace Comsenz.Base
{
    /// <summary>
    /// 安全相关操作
    /// </summary>
    /// <Author>Chiron</Author>
    public static class Security
    {
        /// <summary>
        /// 操作类型
        /// </summary>
        public enum Operations
        {
            /// <summary>
            /// 加密
            /// </summary>
            ENCODE,
            /// <summary>
            /// 解密
            /// </summary>
            DECODE
        }
        /// <summary>
        /// 字符串加密以及解密函数
        /// </summary>
        /// <param name="data">原文或者密文</param>
        /// <param name="key">密钥, 默认为 Config.ConnectKey</param>
        /// <param name="operation">操作(ENCODE | DECODE), 默认为 DECODE</param>
        /// <param name="expiry">密文有效期, 加密时候有效， 单位 秒，0 为永久有效</param>
        /// <param name="ckey_length">
        /// 随机密钥长度 取值 0-32;默认为 4;
        /// 加入随机密钥，可以令密文无任何规律，即便是原文和密钥完全相同，加密结果也会每次不同，增大破解难度。
        /// 取值越大，密文变动规律越大，密文变化 = 16 的 ckey_length 次方.
        /// 当此值为 0 时，则不产生随机密钥.
        /// </param>
        /// <returns>处理后的 原文或者 经过 base64_encode 处理后的密文</returns>
        public static string Authcode(string data, string key, Operations operation = Operations.DECODE, int expiry = 0, int ckey_length = 4)
        {
            if (string.IsNullOrEmpty(data)) throw new ArgumentNullException("data");
            if (string.IsNullOrEmpty(key)) throw new ArgumentNullException("key");

            #region Debug
#if DEBUG
            Debug.WriteLine(null);
            Debug.Print("Debuging uc_authcode {0}.", operation);
            Debug.Print("SourceData:{0}", data);
            Debug.WriteLine(null);

#endif
            #endregion Debug

            if (ckey_length < 0) ckey_length = 0;
            else if (ckey_length > 32) ckey_length = 32;

            key = Cryptography.MD5(key);
            var keya = Cryptography.MD5(key.Substring(0, 16));
            var keyb = Cryptography.MD5(key.Substring(16, 16));
            var keyc = ckey_length > 0 ?
                (operation == Operations.DECODE ? data.Substring(0, ckey_length) : Cryptography.MD5(PhpFunc.Microtime()).Substring(32 - ckey_length)) :
                string.Empty;

            var cryptkey = keya + Cryptography.MD5(keya + keyc);

            var tdata = data;
            if (operation == Operations.DECODE)
            {
                tdata = data.Substring(ckey_length);
                if (tdata.Length % 4 != 0) tdata += new string('=', 4 - (tdata.Length % 4));
            }

            var dataBuf = operation == Operations.DECODE ?
                Convert.FromBase64String(tdata) :
                ((expiry < 1 ? 0 : PhpFunc.Time() + expiry).ToString("000000000#") + Cryptography.MD5(data + keyb).Substring(0, 16) + data).Select(d => (byte)d).ToArray();

            #region Debug
#if DEBUG
            Debug.Print("key:{0}\nkeya:{1}\nkeyb:{2}\nkeyc:{3}\ncryptkey:{4}", key, keya, keyb, keyc, cryptkey);
            Debug.Print("DataBuf:{0}", dataBuf.Select(d => ((int)d).ToString()).Aggregate((d, e) => d + e));
            Debug.WriteLine(null);
#endif
            #endregion Debug

            var rndkey = new char[256];
            var box = new char[256];
            for (var i = 0; i < 256; i++)
            {
                box[i] = (char)i;
                rndkey[i] = cryptkey[i % cryptkey.Length];
            }

            for (int i = 0, j = 0; i < 256; i++)
            {
                j = (j + box[i] + rndkey[i]) % 256;
                var tmp = box[i];
                box[i] = box[j];
                box[j] = tmp;
            }

            #region Debug
#if DEBUG
            Debug.Print("rndkey:{0}", new string(rndkey));
            Debug.Print("box:{0}", box.Select(d => ((int)d).ToString()).Aggregate((d, e) => d + e));
            Debug.WriteLine(null);
#endif
            #endregion Debug

            var resultBuf = new byte[dataBuf.Length];
            for (int a = 0, j = 0, i = 0; i < dataBuf.Length; i++)
            {
                a = (a + 1) % 256;
                j = (j + box[a]) % 256;
                var tmp = box[a];
                box[a] = box[j];
                box[j] = tmp;
                resultBuf[i] = (byte)(dataBuf[i] ^ box[(box[a] + box[j]) % 256]);
            }

            var result = Encoding.UTF8.GetString(resultBuf);    //TODO:非英文字符不能正常解码      Chiron

            #region Debug
#if DEBUG
            Debug.Print("resultBuf:{0}", resultBuf.Select(d => ((int)d).ToString()).Aggregate((d, e) => d + e));
            Debug.Print("result:{0}", result);
            Debug.WriteLine(null);
#endif
            #endregion Debug

            if (operation == Operations.DECODE)
            {
                int tTime;
                int.TryParse(result.Substring(0, 10), out tTime);
                if ((tTime == 0 || tTime - PhpFunc.Time() > 0) && result.Substring(10, 16) == Cryptography.MD5(result.Substring(26) + keyb).Substring(0, 16))
                {
                    return result.Substring(26);
                }
                else
                {
                    return string.Empty;
                }
            }
            else
            {
                return keyc + Convert.ToBase64String(resultBuf).Replace("=", string.Empty);
            }
        }

        /// <summary>
        /// 取得用户代理的MD5编码
        /// </summary>
        /// <returns></returns>
        public static string GetUserAgentMd5Code()
        {
            return HttpContext.Current == null ? "d41d8cd98f00b204e9800998ecf8427e" : Cryptography.MD5(HttpContext.Current.Request.UserAgent);
        }
    }
}
